﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Boggle
{
    /// <summary>
    /// A game of Boggle.
    /// </summary>
    partial class Game : INotifyPropertyChanged
    {
        #region Member Variables

        /// <summary>
        /// The current state of the game.
        /// </summary>
        private GameState currentState;

        /// <summary>
        /// The time that the game was started.
        /// </summary>
        private DateTime startedTime;

        /// <summary>
        /// The game's current time.
        /// </summary>
        private DateTime currentTime;

        /// <summary>
        /// The time that this game is expected to end.
        /// </summary>
        private DateTime expectedEndTime;

		/// <summary>
		/// The length that a single game lasts.
		/// </summary>
		private readonly TimeSpan gameLength;

        /// <summary>
        /// The amount of time in seconds that this game has left.
        /// </summary>
        private Double secondsLeft;

		/// <summary>
		/// The current score for this game.
		/// </summary>
		private Int32 score;

        /// <summary>
        /// The word currently about to be submitted.
        /// </summary>
        private String currentWord;

        /// <summary>
        /// The currently selected word.
        /// </summary>
        private Word selectedWord;

        /// <summary>
        /// All the possible states this game can be in.
        /// </summary>
        private readonly Dictionary<GameState, IGameState> states;

        /// <summary>
        /// The grid used to play Boggle.
        /// </summary>
        private readonly IBoggleGrid grid;

        /// <summary>
        /// The dictionary used to play Boggle.
        /// </summary>
        private IBoggleDictionary dict;

        /// <summary>
        /// The words submitted in this game.
        /// </summary>
        private readonly ObservableCollection<Word> words;

        /// <summary>
        /// A list of word lookup tasks that are yet to be completed.
        /// </summary>
        private readonly List<Tuple<Word, Task<Object>>> wordLookupTasks;

        #endregion

        #region Properties

		public ObservableCollection<Word> Words { get { return words; } }

		public GameState CurrentState
        {
            get { return currentState; }
			private set
			{
				currentState = value;
				OnPropertyChanged(new PropertyChangedEventArgs("CurrentState"));
			}
        }

        public Double SecondsLeft
        {
            get { return secondsLeft; }
            private set
            {
                secondsLeft = value;
                OnPropertyChanged(new PropertyChangedEventArgs("SecondsLeft"));
            }
        }

		public Int32 Score
		{
			get { return score; }
			private set
			{
				score = value;
				OnPropertyChanged(new PropertyChangedEventArgs("Score"));
			}
		}

        public String CurrentWord
        {
            get { return currentWord; }
            set
            {
                currentWord = value;
                OnPropertyChanged(new PropertyChangedEventArgs("CurrentWord"));
            }
        }

        public Word SelectedWord
        {
            get { return selectedWord; }
            set
            {
                selectedWord = value;
                OnPropertyChanged(new PropertyChangedEventArgs("SelectedWord"));
            }
        }

        public IBoggleDictionary Dictionary
        {
            get { return states[currentState].Dictionary; }
            set { states[currentState].Dictionary = value; }
        }

        public IBoggleGrid Grid { get { return grid; } }

		/// <summary>
		/// Gets the amount of time that a game lasts for.
		/// </summary>
		public TimeSpan GameLength { get { return gameLength; } }

        #endregion

        #region Constructor

        public Game(IBoggleGrid grid, IBoggleDictionary dict)
        {
            if (grid == null)
            {
                throw new ArgumentNullException("grid");
            }
            if (dict == null)
            {
                throw new ArgumentNullException("dict");
            }

            this.grid = grid;
            this.dict = dict;
            secondsLeft = 0;
			score = 0;
			gameLength = TimeSpan.FromMinutes(3);
            currentWord = String.Empty;
            words = new ObservableCollection<Word>();
            wordLookupTasks = new List<Tuple<Word, Task<Object>>>();

            states = new Dictionary<GameState, IGameState>
            {
                { GameState.Setup, new SetupState(this) },
                { GameState.InProgress, new InProgressState(this) },
                { GameState.Ended, new EndedState(this) }
            };

            SetState(GameState.Setup);
        }

        #endregion

        #region Methods

        private void UpdateSecondsLeft()
        {
            SecondsLeft = Math.Round((expectedEndTime - currentTime).TotalSeconds);
        }

        private void SetState(GameState gameState)
        {
            CurrentState = gameState;
            states[currentState].Initialize();
        }

        public void Start(DateTime startTime)
        {
            states[currentState].Start(startTime);
        }

        public Boolean Next(DateTime currentTime)
        {
            return states[currentState].Next(currentTime);
        }

        public void SubmitWord()
        {
            states[currentState].SubmitWord();
        }

        public void EndGame()
        {
            states[currentState].EndGame();
        }

        public void NewGame()
        {
            states[currentState].NewGame();
        }

        private void DefineWord(Word word)
        {
            Task<Object> task;

            if (dict.IsDefineSupported)
            {
                task = new Task<Object>(() => dict.Define(word.WordText));
            }
            else
            {
                task = new Task<Object>(() => dict.IsWord(word.WordText));
            }

            wordLookupTasks.Add(new Tuple<Word, Task<Object>>(word, task));
            task.Start();
        }

        private void UpdateWord(Tuple<Word, Task<Object>> taskPair)
        {
            if (taskPair == null)
            {
                throw new ArgumentNullException("taskPair");
            }

            var word = taskPair.Item1;
            var task = taskPair.Item2;

            try
            {
                var result = task.Result;

                if (result is Boolean)
                {
                    var isWord = (Boolean)result;

                    if (isWord)
                    {
                        word.CurrentStatus = Word.Status.Validated;
                        word.Definition = "Word validated.";
                    }
                    else
                    {
                        word.CurrentStatus = Word.Status.NotAWord;
                        word.Definition = "Not a word.";
                    }
                }
                else
                {
                    var defs = (List<String>)result;

                    if (defs == null || defs.Count < 1)
                    {
                        word.CurrentStatus = Word.Status.NotAWord;
                        word.Definition = "Not a word.";
                    }
                    else
                    {
                        word.CurrentStatus = Word.Status.Validated;
                        word.Definition = String.Join(Environment.NewLine, defs);
                    }
                }
            }
            catch (Exception ex)
            {
                word.CurrentStatus = Word.Status.Error;
                word.Definition = ex.ToString();
            }
        }

        private Boolean CompleteLookups()
        {
            var completedLookups = wordLookupTasks.Where(tp => tp.Item2.IsCompleted).ToList();
            completedLookups.ForEach(UpdateWord);
            wordLookupTasks.RemoveAll(l => completedLookups.Contains(l));
			Score = words.Aggregate(0, (s, w) => s + w.Score);
            return wordLookupTasks.Count < 1;
        }

        #endregion

        #region INotifyPropertyChanged Implementation

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            var handler = PropertyChanged;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        #endregion
    }
}
